package org.az.netty_study.p03_nio组件之selector._3_单线程selector;

import lombok.extern.slf4j.Slf4j;
import org.az.netty_study.ByteBufferUtil;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 前面单线程的阻塞模式:线程只能做一件事，等待连接时会一直等，等待连接channel的可读事件时也会一直等
 * 单线程的非阻塞模式:死循环，当一直没有连接和可读事件发生时，也会一直while(true)，白白浪费cpu资源
 * 换一种思路，可以监听所有channel，当任一个channel发生事件时才继续执行，否则就阻塞，并根据事件类型做不同处理
 * 这样可以避免可读事件发生时线程却还在苦苦等待连接事件，也可以避免cpu一直空转。这个监听所有channel的角色，就是Selector
 * 事件类型:
 *     1.accept:有连接请求时触发的事件
 *     2.connect:连接建立后，客户端那边触发的事件
 *     3.read:可读事件，客户端往SocketChannel上写入新数据时，触发的事件
 *     4.write:可写事件
 * 这种单线程配合selector完成对多个channel事件的监控就是典型的多路复用，注意：只有网络io，才能使用多路复用
 */
@Slf4j
public class Server {
    public static void main(String[] args) throws IOException {
        //1.创建selector(管理多个channel)
        Selector selector = Selector.open();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(9997));
        /**
         * 2.将ssc注册到Selector
         *   SelectionKey,相当于管理员，管理一个channel(这里是管理的ServerSocketChannel)，可以监听到其管理的channel上的所有事件
         *   事件发生后，可以通过SelectionKey得到事件类型，ServerSocketChannel只关注accept事件
         *   ssc.register参数: 1)注册到哪个连接器; 2)关注的事件; 3)参数
         *   sscKey.interestOps(SelectionKey.OP_ACCEPT) 表示sscKey只关注accept事件
         */
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        while (true) {
            /**
             * 3.Selector.select，Selector管理的所有channel都没有事件发生，就会阻塞
             *   一旦有事件发生，就会往下运行
             *   事件必须处理，如果事件不处理，会被当成新发生的事件不断循环(处理事件或取消事件，都会认为事件被处理了)
             */
            int select = selector.select();

            /**
             * 4.处理事件
             *   selector.selectedKeys(),会返回所有发生的事件(当事件发生，selector会往selectedKeys集合添加SelectionKey，但不会主动删)
             *   遍历所有发生的事件，根据事件类型进行不同的处理
             *   事件处理完毕后，将SelectionKey移除
             */
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            log.info("selectedKeys集合大小 -> {}", keys.size());
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //处理完的SelectionKey事件，要从selectedKeys集合里删掉，否则会一直在集合中，那么下一次遍历就会出问题
                iterator.remove();
                //5.区分事件类型
                if (key.isAcceptable()) {
                    //是accept事件，则获取与客户端通行的SocketChannel，并注册给selector
                    //key.channel()可以获取到事件是发生在哪个channel上的
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    //处理accept事件
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    //注册SocketChannel到selector，给SelectionKey绑定一个附件，将来这个channel就能拿到这个附件.这里绑定一个buffer
                    SelectionKey sk = sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(16));
                    log.info("建立了一个新连接:{}", sc);

                }
                else if (key.isReadable()) {
                    try {
                        //是read事件(连接断开也会发生read事件)
                        SocketChannel channel = (SocketChannel)key.channel();
                        //拿到绑定给这个key的buffer
                        ByteBuffer buffer = (ByteBuffer)key.attachment();
                        //处理read事件
                        //read为-1，表示客户端正常断开。如果一次没有把channel中的内容读完，那么剩下的内容会在下一次循环触发可读事件
                        int read = channel.read(buffer);
                        if (read == -1) {
                            key.cancel();
                        } else {
                            /**
                             * 区分消息边界(消息以换行符'\n'结尾)
                             * 当一条消息过长时，也就是buffer中没有一个换行符'\n'，就说明buffer小了，需要扩容
                             */
                            buffer.flip();
                            ByteBufferUtil.debugAll(buffer);
                        }
                    } catch (IOException e) {
                        //客户端强制断开连接，channel.read(buffer)会抛出IOException异常
                        e.printStackTrace();
                        //从selector反注册当前SelectionKey
                        key.cancel();
                    }
                }
                //其他事件类型的处理...
            }
        }
    }
}
